#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# LAN Wakey v0.1.3
# Copyright (C) 2012 Nicholas Wilde <ncwilde43@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

# Import Stuff
# ---Standard library modules.
import sys, os, string, sip
sip.setapi("QVariant", 2)
from time import sleep
from xml.dom.minidom import Document
from PyQt4 import QtGui, QtCore
# ---Import 3rd Party Modules.
try:
    import wol # Awake
except ImportError:
    sys.exit('ImportError: Awake module not installed. ' 
        '<https://github.com/cyraxjoe/awake>')
# ---Locally developed modules.
from src.ui.ui_mainwindow import Ui_MainWindow
from src.ui.ui_aboutwindow import Ui_aboutDialog 
import src.ui.images_rc, include.defined
import include.fileops as fileops
import include.window as window
import include.check as check

# Variables 
__appName__ = 'LAN Wakey'
__verFileName__ = 'VERSION'
__configFileName__ = 'settings'
__homePage__ = 'http://code.google.com/p/lanwakey/'
__wikiPage__ = '%sw/list' % __homePage__
__author__ = 'Nicholas Wilde'
__configFileName__ = 'settings'
__dataDirName__ = 'data'
__persistentFile__ ='current.xml'
__sleepTime__ = 2
__defaultPort__ = '9'

# Determine if application is a script file or frozen exe and get appPath
# http://stackoverflow.com/a/404750/1061279
if hasattr(sys, 'frozen'): __appPath__ = os.path.dirname(sys.executable)
elif __file__: __appPath__ = os.path.dirname(__file__)
__verPath__ = os.path.join(__appPath__, __verFileName__)

# Get qrdecoder version from VERSION file
f = open(__verPath__, 'r')
__version__ = f.readline()
f.close()

#=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ABOUT WINDOW =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

# Common functions to all classes
class Common:
    # Open up the homepage
    def openHomepage(self):
        self.openUpUrl(__homePage__)
        
    # Open a URL
    def openUpUrl(self, url):
        QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
        
# About window properties
class AboutDialog(QtGui.QDialog, Ui_aboutDialog, Common):
    def __init__(self, parent=None):
        super(QtGui.QDialog, self).__init__(parent)
        self.setupUi(self)
        self.setWindowTitle("About")
        # Info displayed in the About window
        __info__ = ("<b>"+__appName__+" v" + __version__ + "</b>"
            "<p>Author: "+__author__+
            "<p>E-mail: <a href='mailto:ncwilde43@gmail.com'>ncwilde43@gmail.com</a>"
            "<p>Home page: <a href='"+__homePage__+"'>http://lanwakey.googlecode.com</a>"
            "<p>License: <a href='http://www.gnu.org/licenses/gpl-3.0.txt'>GNU GPLv3</a>"
            )
        self.label.setText(__info__)
        self.connect(QtGui.QShortcut(QtGui.QKeySequence("Ctrl+W"),self),
            QtCore.SIGNAL("activated()"), QtCore.SLOT("accept()"))
        self.connect(self.pushButton_OK, QtCore.SIGNAL("clicked()"),
            QtCore.SLOT("accept()"))
        self.connect(self.pushButton, QtCore.SIGNAL("clicked()"), self.openHomepage)
        del __info__ 
        
#=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- MAIN WINDOW =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

# Main Window properties
class lanWakey(QtGui.QMainWindow, Ui_MainWindow, Common):
    def __init__(self):
        super(QtGui.QMainWindow, self).__init__()
        self.setupUi(self)

        # Read settings from settings.ini file
        __settingsDir__ = self.settingsFile()
        self.readSettings()
        
        # Format paths
        __dataDir__ = __settingsDir__ + '/' + __dataDirName__
        global __persistentFileName__
        __persistentFileName__ = __dataDir__ + '/' + __persistentFile__
        
        # Check if data dir exists
        if not os.path.exists(__dataDir__):
            os.makedirs(__dataDir__)
            
        # Have window accept drops
        self.setAcceptDrops(True)

        # Fix window size
        self.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize)
        
        # Set window title
        self.setWindowTitle(__appName__+" - v"+__version__)
        
        # Button Commands
        self.connect(self.pushButton_new, QtCore.SIGNAL("clicked()"),
            self.newFile)
        self.connect(self.pushButton_import, QtCore.SIGNAL("clicked()"),
            self.selectFile)
        self.connect(self.pushButton_wake, QtCore.SIGNAL("clicked()"),
            self.wake)
        self.connect(self.pushButton_exit, QtCore.SIGNAL("clicked()"),
            self.exitApplication)     
        
        # Menu Commands
        self.connect(self.actionNew, QtCore.SIGNAL("triggered()"), self.newFile)
        self.connect(self.actionImport, QtCore.SIGNAL("triggered()"),
            self.selectFile)
        self.connect(self.actionExport, QtCore.SIGNAL("triggered()"),
            self.saveFile)
        self.connect(self.actionExit, QtCore.SIGNAL("triggered()"),
            self.exitApplication)
        self.connect(self.actionHomepage, QtCore.SIGNAL("triggered()"),
            self.openHomepage)
        self.connect(self.actionWiki, QtCore.SIGNAL("triggered()"),
            self.openWiki)
        self.connect(self.actionAbout, QtCore.SIGNAL("triggered()"), self.about)

        # Checkbox Commands
        self.connect(self.checkBox_destination,
            QtCore.SIGNAL("stateChanged(int)"), self.triggerDestination)
        self.connect(self.checkBox_port, QtCore.SIGNAL("stateChanged(int)"),
            self.triggerPort)
        
        # Set fields order
        self.setTabOrder(self.lineEdit_mac, self.lineEdit_broadcast)
        self.setTabOrder(self.lineEdit_broadcast, self.lineEdit_destination)
        self.setTabOrder(self.lineEdit_destination, self.lineEdit_port)
        self.setTabOrder(self.lineEdit_port, self.checkBox_destination)
        self.setTabOrder(self.checkBox_destination, self.checkBox_port)
        self.setTabOrder(self.checkBox_port, self.pushButton_new)
        self.setTabOrder(self.pushButton_new, self.pushButton_import)
        self.setTabOrder(self.pushButton_import, self.pushButton_wake)
        self.setTabOrder(self.pushButton_wake, self.pushButton_exit)
        
        # Set button statusTips to same as menu
        self.pushButton_import.setStatusTip(self.actionImport.statusTip())
        self.pushButton_new.setStatusTip(self.actionNew.statusTip())
        self.pushButton_exit.setStatusTip(self.actionExit.statusTip())
        
        # Add placeholderText to lineEdit fields which was added in Qt 4.7
        # http://stackoverflow.com/a/5876666
        if hex(QtCore.QT_VERSION) >= 0x407000 or defined(Q_WS_MAEMO_5):
            self.lineEdit_mac.setPlaceholderText('ex. 00:00:00:00:00:00')
            self.lineEdit_broadcast.setPlaceholderText('ex. 192.168.1.255')
            self.lineEdit_destination.setPlaceholderText('ex. 192.168.1.100')

        # Fill in old settings if Persistent settings is checked
        if self.getPersistentValueFromFile() == 'true' \
            and os.path.isfile(__persistentFileName__):
            self.selectFile(__persistentFileName__)
        else: self.clearFields()

    #=-=-=-=-=-=-=-=-=-=-=-=-=-=- BUTTON CONTROLS =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
    
    # Actions to perform once destination check box is checked.
    def triggerDestination(self):
        if self.checkBox_destination.checkState():
            self.checkBox_destination.setStatusTip('Disable destination address.')
            self.lineEdit_destination.setEnabled(1)
            self.lineEdit_broadcast.setEnabled(0)
        else:
            self.checkBox_destination.setStatusTip('Enable destination address.')
            self.lineEdit_destination.setEnabled(0)
            self.lineEdit_broadcast.setEnabled(1)
        
    # Actions to perform once port check box is checked.
    def triggerPort(self):
        if self.checkBox_port.checkState():
            self.lineEdit_port.setEnabled(1)
        else:
            self.lineEdit_port.setEnabled(0)
            self.lineEdit_port.setText(__defaultPort__)

    # Clear all fields and reset states
    def clearFields(self):
        # Reset check boxes
        for child in self.groupBox.findChildren(QtGui.QCheckBox):
            child.setTristate(0)
            child.setCheckState(0)
        
        # Clear lineEdit fields
        for child in self.groupBox.findChildren(QtGui.QLineEdit):
            child.clear()
            
        self.triggerDestination()
        self.triggerPort()
        self.lineEdit_port.setText(__defaultPort__)
        self.pushButton_wake.setFocus()
    
    #=-=-=-=-=-=-=-=-=-=-=-=-=-=- MENU CONTROLS =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 
    # Open wiki page from menu item.
    def openWiki(self):
        self.openUpUrl(__wikiPage__)
    
    # Command used to save a file.
    def saveFile(self, fileName=None):
        fileops.exportFile(self)
        
    # Reset all fields when new button is pressed.
    def newFile(self):
        self.clearFields()
 
    # Run when import button/menu item is selected.
    def selectFile(self, fileName=None):
        if fileName: fileops.importFile(self, fileName)
        else: fileops.importFile(self)
        self.triggerDestination()
        self.triggerPort()
 
    # Call the About window
    def about(self):
        dialog = AboutDialog(self)
        dialog.exec_()
        
    #=-=-=-=-=-=-=-=-=-=-=-=-=-=-= MISCELLANEOUS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

    # Get the persistent menu option status.
    def getPersistentValue(self):
        return self.actionPersistent.isChecked()
    
    # Reformat the mac address if dashes are used instead of colons.
    def reformatMac(self, mac):
        if "-" in mac:
            mac = mac.replace ("-", ":")
            self.lineEdit_mac.setText(mac)
        return mac

    # Display statusBar messages.  
    def showMessage(self, message, color=False):
        if color: self.statusBar().setStyleSheet('color:%s' % color)
        self.statusBar().showMessage(message, 0)
        sleep(__sleepTime__)
        self.statusBar().setStyleSheet('color:black')
        self.statusBar().clearMessage()

    #=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- DRAG & DROP =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

    # Determine if drag even should happen
    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls(): event.accept()
        else: event.ignore()
            
    # What to do when the drag event happens
    def dropEvent(self, event):
        data = event.mimeData()
        for url in data.urls():
            fileName = url.toLocalFile()
            fileInfo = QtCore.QFileInfo(fileName)
            if fileInfo.isFile(): self.selectFile(str(fileName))

    #=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- SETTINGS =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
    # Read settings on startup
    def readSettings(self):
        numScreens = QtGui.QDesktopWidget().numScreens()
        screen = QtGui.QDesktopWidget().availableGeometry(numScreens-1) 
        size = self.geometry()
        # Get edge locations of window
        if self.settings.value("MainWindow/x"):
            windowLeft = int(self.settings.value("MainWindow/x"))
            windowRight = int(self.settings.value("MainWindow/x")) + size.width()
            windowTop = int(self.settings.value("MainWindow/y"))
            windowBottom = int(self.settings.value("MainWindow/y")) + size.height()
            screenRight = int(screen.width()) + screen.x()
            screenLeft = QtGui.QDesktopWidget().availableGeometry().x()
            screenBottom = int(screen.height()) + screen.y()
            screenTop = QtGui.QDesktopWidget().availableGeometry().y()
            
            # If part of the window is off the screen, center it.
            if (windowRight > screenRight) \
                or (windowBottom > screenBottom) \
                or (windowLeft < screenLeft) \
                or (windowTop < screenTop):
                window.centerWindow(self)
            else: window.moveWindow(self)
        else: window.centerWindow(self)
            
        # Set Persistent setting
        if self.getPersistentValueFromFile() == "true":
            self.actionPersistent.setChecked(True)
        else: self.actionPersistent.setChecked(False)

    # Determine where to save settings file
    def settingsFile(self):
        global __configFileName__
        if sys.platform.startswith('win'):
            __configFileName__ += '.ini'
            __configFilePath__ = os.path.join(__appPath__, __configFileName__)
            self.settings = QtCore.QSettings(__configFilePath__,
                QtCore.QSettings.IniFormat)
            # Use %appdata% if install dir is read only
            if not self.settings.isWritable(): 
                self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
                    QtCore.QSettings.UserScope,
                    __appName__, __configFileName__)
        elif sys.platform.startswith('lin'): # Read settings.conf file
            self.settings = QtCore.QSettings(__appName__.lower(),
                __configFileName__)
        else:
            self.settings = QtCore.QSettings(__appName__.lower(),
                __configFileName__)
        # http://stackoverflow.com/a/915160
        return str(QtCore.QFileInfo(self.settings.fileName()).absolutePath())

    # Save application settings
    def writeSettings(self):
        # Save window location
        self.settings.beginGroup("MainWindow")
        self.settings.setValue("x", self.x())
        self.settings.setValue("y", self.y())
        self.settings.endGroup()
        
        # Save Persistence setting
        self.settings.beginGroup("Settings")
        self.settings.setValue("persistent", self.getPersistentValue())
        self.settings.endGroup()

    # Get Persistent value from settings file
    def getPersistentValueFromFile(self):
        return str(self.settings.value("Settings/persistent"))

    # Write inputs to file if Persistent setting is checked.
    def writePersistentFile(self):
        fileops.exportFile(self, __persistentFileName__)

    #=-=-=-=-=-=-=-=-=-=-=-=-=-=- MAIN CONTROLS =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

    # Wake the computer
    def wake(self):
        # Get all input fields
        mac = self.reformatMac(str(self.lineEdit_mac.text()))
        if not check.checkMac(mac):
            self.showMessage('Invalid MAC address.', 'red')
            return
        if self.checkBox_destination.checkState():
            destination = self.lineEdit_destination.text()
            if not destination:
                self.showMessage('Fill in destination field.','red')
                return
            message = 'sent to %s' % destination
            broadcast = None
        else:
            destination = None
            # Check for broadcast address integrity.
            broadcast = self.lineEdit_broadcast.text()
            if not check.checkBroadcast(broadcast):
                self.showMessage('Invalid broadcast address.', 'red')
                return
            message = 'broadcasted to %s' % broadcast
        port = self.lineEdit_port.text()
        if not check.checkPort(port):
            self.showMessage('Invalid UDP port.', 'red')
            return
        
        # Send magic packet.
        try:
            wol.wol(mac, broadcast, destination, int(port))
            self.showMessage('Magic packet %s.' % message)
        except IOError: self.showMessage('Magic packet error.', 'red')
        sleep(__sleepTime__)
      
    #=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- EXIT =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
    # Call exitApplication() when "x" close button is pressed.
    def closeEvent(self, event):
        self.onClose()
        event.accept()
        
    # Actions to perform on application close
    def onClose(self):
        self.writeSettings()
        # Determine if we need to save inputs
        if self.getPersistentValue(): self.writePersistentFile()
        elif os.path.isfile(__persistentFileName__):
            os.remove(__persistentFileName__)
    
    # Exit application.
    def exitApplication(self):
        self.close()
        
if __name__ == "__main__":
    application = QtGui.QApplication(sys.argv)
    application.setOrganizationName(__author__)
    application.setOrganizationDomain(__homePage__)
    application.setApplicationName(__appName__)
    mainWindow = lanWakey()
    mainWindow.show()
    sys.exit(application.exec_())